/*******************************************************************************
 * Copyright (c) 2016 Rogue Wave Software, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Rogue Wave Software, Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.plugin.zdb.server.connection;

import java.io.DataInputStream;
import java.io.IOException;
import java.net.URLDecoder;

import org.eclipse.che.plugin.zdb.server.connection.ZendDbgClientMessages.GetLocalFileContentResponse;
import org.eclipse.che.plugin.zdb.server.connection.ZendDbgClientMessages.IDbgClientResponse;
import org.eclipse.che.plugin.zdb.server.utils.ZendDbgConnectionUtils;

/**
 * Zend debug engine messages container.
 *
 * @author Bartlomiej Laczkowski
 */
public class ZendDbgEngineMessages {

    // Notification types
    public static final int NOTIFICATION_SRIPT_ENDED = 2002;
    public static final int NOTIFICATION_READY = 2003;
    public static final int NOTIFICATION_OUTPUT = 2004;
    public static final int NOTIFICATION_SESSION_STARTED = 2005;
    public static final int NOTIFICATION_PARSING_ERROR = 2006;
    public static final int NOTIFICATION_DEBUGGER_ERROR = 2007;
    public static final int NOTIFICATION_HEADER_OUTPUT = 2008;
    public static final int NOTIFICATION_START_PROCESS_FILE = 2009;
    public static final int NOTIFICATION_INI_ALTERED = 2011;
    public static final int NOTIFICATION_CLOSE_MESSAGE_HANDLER = 0;
    // Response types
    public static final int RESPONSE_START = 1001;
    public static final int RESPONSE_PAUSE_DEBUGGER = 1002;
    public static final int RESPONSE_STEP_INTO = 1011;
    public static final int RESPONSE_STEP_OVER = 1012;
    public static final int RESPONSE_STEP_OUT = 1013;
    public static final int RESPONSE_GO = 1014;
    public static final int RESPONSE_ADD_BREAKPOINT = 1021;
    public static final int RESPONSE_DELETE_BREAKPOINT = 1022;
    public static final int RESPONSE_DELETE_ALL_BREAKPOINTS = 1023;
    public static final int RESPONSE_EVAL = 1031;
    public static final int RESPONSE_GET_VARIABLE_VALUE = 1032;
    public static final int RESPONSE_ASSIGN_VALUE = 1033;
    public static final int RESPONSE_GET_CALL_STACK = 1034;
    public static final int RESPONSE_GET_STACK_VARIABLE_VALUE = 1035;
    public static final int RESPONSE_GET_CWD = 1036;
    public static final int RESPONSE_ADD_FILES = 1038;
    public static final int RESPONSE_SET_PROTOCOL = 11000;
    public static final int RESPONSE_UNKNOWN = 1000;
    // Request types
    public static final int REQUEST_GET_LOCAL_FILE_CONTENT = 10002;

    public interface IDbgEngineMessage extends IDbgMessage {

        /**
         * De-serialize this debug message from an input stream
         *
         * @param in
         *            input stream this message is going to be read from
         */
        public void deserialize(DataInputStream in) throws IOException;

    }

    public interface IDbgEngineNotification extends IDbgEngineMessage {
    }

    public interface IDbgEngineRequest<T extends IDbgClientResponse> extends IDbgEngineMessage {

        /**
         * Return the request id.
         */
        public int getID();

    }

    public interface IDbgEngineResponse extends IDbgEngineMessage {

        /**
         * Return the response id.
         */
        public int getID();

        /**
         * Return the response status.
         */
        public int getStatus();

    }

    private static abstract class AbstractEngineRequest<T extends IDbgClientResponse> extends AbstractDbgMessage
            implements IDbgEngineRequest<T> {

        private int id;

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            id = in.readInt();
        }

        @Override
        public int getID() {
            return id;
        }

    }

    private static abstract class AbstractEngineResponse extends AbstractDbgMessage implements IDbgEngineResponse {

        protected int id;
        protected int status;

        @Override
        public int getID() {
            return this.id;
        }

        @Override
        public int getStatus() {
            return status;
        }

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            id = in.readInt();
            status = in.readInt();
        }
    }

    private ZendDbgEngineMessages() {
    }

    // Engine notifications

    public static class DebuggerErrorNotification extends AbstractDbgMessage implements IDbgEngineNotification {

        private int errorLevel = 0;
        private String errorText;

        @Override
        public int getType() {
            return NOTIFICATION_DEBUGGER_ERROR;
        }

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            errorLevel = in.readInt();
            errorText = ZendDbgConnectionUtils.readString(in);
        }

        public int getErrorLevel() {
            return errorLevel;
        }

        public String getErrorText() {
            return errorText;
        }
    }

    public static class ScriptEndedNotification extends AbstractDbgMessage implements IDbgEngineNotification {

        private int status;

        @Override
        public int getType() {
            return NOTIFICATION_SRIPT_ENDED;
        }

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            status = in.readInt();
        }

        public int getStatus() {
            return status;
        }
    }

    public static class SessionStartedNotification extends AbstractDbgMessage implements IDbgEngineNotification {

        private String fileName = "";
        private String uri = "";
        private String query = "";
        private String additionalOptions = "";
        private int protocolID;

        @Override
        public int getType() {
            return NOTIFICATION_SESSION_STARTED;
        }

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            protocolID = in.readInt();
            fileName = ZendDbgConnectionUtils.readString(in);
            uri = ZendDbgConnectionUtils.readString(in);
            query = URLDecoder.decode(ZendDbgConnectionUtils.readString(in), "UTF-8");
            additionalOptions = ZendDbgConnectionUtils.readString(in);
        }

        public String getFileName() {
            return fileName;
        }

        public String getUri() {
            return uri;
        }

        public String getQuery() {
            return query;
        }

        public String getOptions() {
            return additionalOptions;
        }

        public int getServerProtocolID() {
            return protocolID;
        }
    }

    public static class HeaderOutputNotification extends AbstractDbgMessage implements IDbgEngineNotification {

        private String output;

        @Override
        public int getType() {
            return NOTIFICATION_HEADER_OUTPUT;
        }

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            output = ZendDbgConnectionUtils.readString(in);
        }

        public String getOutput() {
            return this.output;
        }
    }

    public static class IniAlteredNotification extends AbstractDbgMessage implements IDbgEngineNotification {

        private String name;
        private String oldValue;
        private String newValue;

        @Override
        public int getType() {
            return NOTIFICATION_INI_ALTERED;
        }

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            name = ZendDbgConnectionUtils.readString(in);
            oldValue = ZendDbgConnectionUtils.readString(in);
            newValue = ZendDbgConnectionUtils.readString(in);
        }

        public String getName() {
            return name;
        }

        public String getOldValue() {
            return oldValue;
        }

        public String getNewValue() {
            return newValue;
        }
    }

    public static class OutputNotification extends AbstractDbgMessage implements IDbgEngineNotification {

        private String output = null;

        @Override
        public int getType() {
            return NOTIFICATION_OUTPUT;
        }

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            output = ZendDbgConnectionUtils.readEncodedString(in, getTransferEncoding());
        }

        public String getOutput() {
            return output;
        }
    }

    public static class ParsingErrorNotification extends AbstractDbgMessage implements IDbgEngineNotification {

        private int errorLevel = 0;
        private String fileName;
        private int lineNumber;
        private String errorText;

        @Override
        public int getType() {
            return NOTIFICATION_PARSING_ERROR;
        }

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            errorLevel = in.readInt();
            fileName = ZendDbgConnectionUtils.readString(in);
            lineNumber = in.readInt();
            errorText = ZendDbgConnectionUtils.readString(in);
        }

        public int getErrorLevel() {
            return this.errorLevel;
        }

        public String getErrorText() {
            return this.errorText;
        }

        public String getFileName() {
            return this.fileName;
        }

        public int getLineNumber() {
            return this.lineNumber;
        }
    }

    public static class ReadyNotification extends AbstractDbgMessage implements IDbgEngineNotification {

        private String fileName;
        private int lineNumber;

        @Override
        public int getType() {
            return NOTIFICATION_READY;
        }

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            fileName = ZendDbgConnectionUtils.readString(in);
            lineNumber = in.readInt();
            in.readInt(); // Read the 4 bytes of the watched-list length. this
                          // is 0 now.
        }

        public String getFileName() {
            return fileName;
        }

        public int getLineNumber() {
            return lineNumber;
        }
    }

    public static class StartProcessFileNotification extends AbstractDbgMessage implements IDbgEngineNotification {

        private String fileName;

        @Override
        public int getType() {
            return NOTIFICATION_START_PROCESS_FILE;
        }

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            fileName = ZendDbgConnectionUtils.readString(in);
        }

        public String getFileName() {
            return fileName;
        }
    }

    // Phantom message used to notify that connection was closed
    public static class CloseMessageHandlerNotification extends AbstractDbgMessage implements IDbgEngineNotification {

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            // dummy
        }

        @Override
        public int getType() {
            return NOTIFICATION_CLOSE_MESSAGE_HANDLER;
        }
    }

    // Engine responses

    public static class AddBreakpointResponse extends AbstractEngineResponse {

        private int breakPointID;

        public int getBreakpointID() {
            return breakPointID;
        }

        @Override
        public int getType() {
            return RESPONSE_ADD_BREAKPOINT;
        }

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            super.deserialize(in);
            breakPointID = in.readInt();
        }
    }

    public static class AddFilesResponse extends AbstractEngineResponse {

        @Override
        public int getType() {
            return RESPONSE_ADD_FILES;
        }
    }

    public static class AssignValueResponse extends AbstractEngineResponse {

        @Override
        public int getType() {
            return RESPONSE_ASSIGN_VALUE;
        }
    }

    public static class DeleteAllBreakpointsResponse extends AbstractEngineResponse {

        @Override
        public int getType() {
            return RESPONSE_DELETE_ALL_BREAKPOINTS;
        }
    }

    public static class DeleteBreakpointResponse extends AbstractEngineResponse {

        @Override
        public int getType() {
            return RESPONSE_DELETE_BREAKPOINT;
        }
    }

    public static class EvalResponse extends AbstractEngineResponse {

        private String result;

        @Override
        public int getType() {
            return RESPONSE_EVAL;
        }

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            super.deserialize(in);
            result = ZendDbgConnectionUtils.readString(in);
        }

        public String getResult() {
            return result;
        }
    }

    public static class GetCallStackResponse extends AbstractEngineResponse {

        @Override
        public int getType() {
            return RESPONSE_GET_CALL_STACK;
        }

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            id = in.readInt();
            // Just read data for now and do nothing with it...
            int depth = in.readInt();
            for (int i = 0; i < depth; i++) {
                ZendDbgConnectionUtils.readString(in);
                in.readInt();
                ZendDbgConnectionUtils.readString(in);
                ZendDbgConnectionUtils.readString(in);
                in.readInt();
                ZendDbgConnectionUtils.readString(in);
                int params = in.readInt();
                for (int j = 0; j < params; j++) {
                    ZendDbgConnectionUtils.readEncodedString(in, getTransferEncoding());
                    ZendDbgConnectionUtils.readStringAsBytes(in);
                }
            }
        }
    }

    public static class GetCWDResponse extends AbstractEngineResponse {

        private String cwd;

        @Override
        public int getType() {
            return RESPONSE_GET_CWD;
        }

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            super.deserialize(in);
            cwd = ZendDbgConnectionUtils.readString(in);
        }

        public String getCWD() {
            return cwd;
        }
    }

    public static class GetStackVariableValueResponse extends AbstractEngineResponse {

        private byte[] varResult;

        @Override
        public int getType() {
            return RESPONSE_GET_STACK_VARIABLE_VALUE;
        }

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            super.deserialize(in);
            varResult = ZendDbgConnectionUtils.readStringAsBytes(in);
        }

        public byte[] getVarResult() {
            return varResult;
        }
    }

    public static class GetVariableValueResponse extends AbstractEngineResponse {

        private byte[] variableValue = null;

        @Override
        public int getType() {
            return RESPONSE_GET_VARIABLE_VALUE;
        }

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            super.deserialize(in);
            variableValue = ZendDbgConnectionUtils.readStringAsBytes(in);
        }

        public byte[] getVariableValue() {
            return variableValue;
        }
    }

    public static class GoResponse extends AbstractEngineResponse {

        @Override
        public int getType() {
            return RESPONSE_GO;
        }
    }

    public static class PauseDebuggerResponse extends AbstractEngineResponse {

        @Override
        public int getType() {
            return RESPONSE_PAUSE_DEBUGGER;
        }
    }

    public static class SetProtocolResponse extends AbstractEngineResponse {

        private int protocolID;

        @Override
        public int getType() {
            return RESPONSE_SET_PROTOCOL;
        }

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            id = in.readInt();
            protocolID = in.readInt();
        }

        public int getProtocolID() {
            return protocolID;
        }
    }

    public static class StartResponse extends AbstractEngineResponse {

        @Override
        public int getType() {
            return RESPONSE_START;
        }
    }

    public static class StepIntoResponse extends AbstractEngineResponse {

        @Override
        public int getType() {
            return RESPONSE_STEP_INTO;
        }
    }

    public static class StepOutResponse extends AbstractEngineResponse {

        @Override
        public int getType() {
            return RESPONSE_STEP_OUT;
        }
    }

    public static class StepOverResponse extends AbstractEngineResponse {

        @Override
        public int getType() {
            return RESPONSE_STEP_OVER;
        }
    }

    public static class UnknownMessageResponse extends AbstractEngineResponse {

        private int origint;

        @Override
        public int getType() {
            return RESPONSE_UNKNOWN;
        }

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            id = in.readInt();
            origint = in.readInt();
        }

        public int getOriginalInt() {
            return origint;
        }
    }

    // Engine requests

    public static class GetLocalFileContentRequest extends AbstractEngineRequest<GetLocalFileContentResponse> {

        private String fileName;
        private int size;
        private int checkSum;

        @Override
        public int getType() {
            return REQUEST_GET_LOCAL_FILE_CONTENT;
        }

        @Override
        public void deserialize(DataInputStream in) throws IOException {
            super.deserialize(in);
            fileName = ZendDbgConnectionUtils.readString(in);
            size = in.readInt();
            checkSum = in.readInt();
        }

        public String getFileName() {
            return fileName;
        }

        public int getSize() {
            return size;
        }

        public int getCheckSum() {
            return checkSum;
        }

    }

    public static IDbgEngineMessage create(int type) {
        switch (type) {
        // Engine notifications
        case NOTIFICATION_DEBUGGER_ERROR:
            return new DebuggerErrorNotification();
        case NOTIFICATION_HEADER_OUTPUT:
            return new HeaderOutputNotification();
        case NOTIFICATION_INI_ALTERED:
            return new IniAlteredNotification();
        case NOTIFICATION_OUTPUT:
            return new OutputNotification();
        case NOTIFICATION_PARSING_ERROR:
            return new ParsingErrorNotification();
        case NOTIFICATION_READY:
            return new ReadyNotification();
        case NOTIFICATION_SESSION_STARTED:
            return new SessionStartedNotification();
        case NOTIFICATION_SRIPT_ENDED:
            return new ScriptEndedNotification();
        case NOTIFICATION_START_PROCESS_FILE:
            return new StartProcessFileNotification();
        // Engine responses
        case RESPONSE_ADD_BREAKPOINT:
            return new AddBreakpointResponse();
        case RESPONSE_ADD_FILES:
            return new AddFilesResponse();
        case RESPONSE_ASSIGN_VALUE:
            return new AssignValueResponse();
        case RESPONSE_DELETE_ALL_BREAKPOINTS:
            return new DeleteAllBreakpointsResponse();
        case RESPONSE_DELETE_BREAKPOINT:
            return new DeleteBreakpointResponse();
        case RESPONSE_EVAL:
            return new EvalResponse();
        case RESPONSE_GET_CALL_STACK:
            return new GetCallStackResponse();
        case RESPONSE_GET_CWD:
            return new GetCWDResponse();
        case RESPONSE_GET_STACK_VARIABLE_VALUE:
            return new GetStackVariableValueResponse();
        case RESPONSE_GET_VARIABLE_VALUE:
            return new GetVariableValueResponse();
        case RESPONSE_GO:
            return new GoResponse();
        case RESPONSE_PAUSE_DEBUGGER:
            return new PauseDebuggerResponse();
        case RESPONSE_SET_PROTOCOL:
            return new SetProtocolResponse();
        case RESPONSE_START:
            return new StartResponse();
        case RESPONSE_STEP_INTO:
            return new StepIntoResponse();
        case RESPONSE_STEP_OUT:
            return new StepOutResponse();
        case RESPONSE_STEP_OVER:
            return new StepOverResponse();
        // Engine requests
        case REQUEST_GET_LOCAL_FILE_CONTENT:
            return new GetLocalFileContentRequest();
        }
        return null;
    }

}
